package airacrobats.game.object;

import airacrobats.io.data.JWavefrontModel;
import airacrobats.sound.Sound3D;
import java.util.List;
import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.swing.JPanel;
import javax.vecmath.Point3f;
import javax.vecmath.Vector3f;

/**
 * This class represents a terrain object.
 */
public class Terrain extends GameObject
{
    /**
     * The minimum height of the shadow.
     */
    public final static float SHADOW_MIN_HEIGHT = 0.3f;
    /**
     * The terrain size (from the origin).
     */
    public final static float LENGTH = 1000.0f;

    /**
     * The objects that will have their shadow projected on the terrain.
     */
    private List<GameObject> shadowObjects;
    /**
     * Light (sun) position.
     */
    private Point3f lightPosition;
    /**
     * The projection matrix, for the shadow.
     */
    private float[] projectionMatrix;

    /**
     * @param model The model.
     * @param shadowObjects The shadow objects.
     * @param lightPosition The light position.
     * @param cameraSight The camera maximum sight.
     */
    public Terrain(JWavefrontModel model, List<GameObject> shadowObjects, Point3f lightPosition, float cameraSight)
    {
        super(model, null, new Point3f(0.0f, 0.0f, 0.0f), new Sound3D[] { });
        this.shadowObjects = shadowObjects;
        this.lightPosition = lightPosition;

        projectionMatrix = new float[16];
    }

    /**
     */
    @Override
    public void transformModelview(GL2 gl)
    {

    }
    
    /**
     */
    public void setupInfoPanel(JPanel owner)
    {
    }
    
    /**
     */
    protected void doDraw(GLAutoDrawable drawable, JWavefrontModel model)
    {
        GL2 gl = drawable.getGL().getGL2();

        gl.glPushMatrix();

        gl.glScalef(LENGTH, LENGTH, LENGTH);
        model.draw(drawable);

        gl.glPopMatrix();

        gl.glDisable(GL2.GL_LIGHTING);
        gl.glDisable(gl.GL_BLEND);

        // Setting the shadow color.
        gl.glColor4d(0.1, 0.1, 0.1, 1);

        for (GameObject shadow : shadowObjects)
        {
            Vector3f normal = new Vector3f();
            float height = SHADOW_MIN_HEIGHT + LENGTH * (calculatePointFace(new Point3f(
                    shadow.getPosition().x / LENGTH, shadow.getPosition().y / LENGTH,
                    shadow.getPosition().z / LENGTH), normal));

            setProjectionMatrix(normal);
            
            gl.glPushMatrix();

            gl.glTranslatef(0.0f, height, 0.0f);
            gl.glMultMatrixf(projectionMatrix, 0);

            shadow.drawShadow(drawable);

            gl.glPopMatrix();
        }

        gl.glEnable(gl.GL_BLEND);
        gl.glEnable(GL2.GL_LIGHTING);
    }

    /**
     */
    public void doUpdate(long deltaTime, long time, Point3f cameraPosition)
    {
    }

    /**
     * Given a point on the space, calculates the face of the terrain object that it is over.
     * @param point The point.
     * @param faceNormal The normal of the plane the point is over.
     * @return The height of the point projected on the surface.
     */
    public float calculatePointFace(Point3f point, Vector3f faceNormal)
    {
        Point3f cP1 = null, cP2 = null, cP3 = null;
        float closestDistance = Integer.MAX_VALUE;

        float[] vertices = model.getVertices();
        JWavefrontModel.Triangle[] triangles = model.getTriangles();

        for (JWavefrontModel.Triangle triangle : triangles)
        {
            Point3f p1 = new Point3f(vertices[triangle.vindices[0] * 3],
                    vertices[triangle.vindices[0] * 3 + 1], vertices[triangle.vindices[0] * 3 + 2]);
            Point3f p2 = new Point3f(vertices[triangle.vindices[1] * 3],
                    vertices[triangle.vindices[1] * 3 + 1], vertices[triangle.vindices[1] * 3 + 2]);
            Point3f p3 = new Point3f(vertices[triangle.vindices[2] * 3],
                    vertices[triangle.vindices[2] * 3 + 1], vertices[triangle.vindices[2] * 3 + 2]);

            Point3f center = new Point3f((p1.x + p2.x + p3.x) / 3.0f, 0,
                    (p1.z + p2.z + p3.z) / 3.0f);

            if (center.distance(point) < closestDistance)
            {
                closestDistance = center.distance(point);
                cP1 = p1;
                cP2 = p2;
                cP3 = p3;
            }
        }

        Vector3f v1 = new Vector3f(cP2.x - cP1.x, cP2.y - cP1.y, cP2.z - cP1.z);
        Vector3f v2 = new Vector3f(cP3.x - cP1.x, cP3.y - cP1.y, cP3.z - cP1.z);
        Vector3f n = new Vector3f();
        n.cross(v1, v2);

        float height = 0;
        if (n.y != 0)
            height = cP1.y - (n.x * (point.x - cP1.x) + n.z * (point.z - cP1.z)) / n.y;

        faceNormal.set(0.0f, 1.0f, 0.0f);
        // faceNormal.set(n);

        return height;
    }

    /**
     * Sets the projection matrix to draw the shadow.
     * @param normal The normal of the plane where the shadow is going to be drawn.
     */
    private void setProjectionMatrix(Vector3f normal)
    {
        float n[] =
        {
            normal.x, normal.y, normal.z, 0.0f
        };
        float L[] =
        {
            lightPosition.x, lightPosition.y, lightPosition.z, 1
        };

        float alfa = 0;
        for (int i = 0; i < 4; i++)
        {
            alfa += n[i] * L[i];
        }

        for (int col = 0; col < 4; col++)
        {
            for (int row = 0; row < 4; row++)
            {
                projectionMatrix[4 * col + row] = -n[col] * L[row];
                if (col == row)
                {
                    projectionMatrix[4 * col + row] += alfa;
                }
            }
        }
    }
}
